/**
 * WeibullDistribution : Definition de la loi de Weibull
 *
 * @author maria ladoue
 * @version 0.1, 06/08/02
 */
package br.unb.statistic.distribution; // Permet de specifier que cette classe appartient au package Statistique

import java.io.IOException;
import java.util.HashMap;
import JSci.maths.SpecialMath;
import br.unb.statistic.InterPacketTimeGenerator;
import br.unb.statistic.RandomGenerator;
import br.unb.statistic.inference.Inference;
import br.unb.utils.Console;
import br.unb.utils.Resource;

public class WeibullDistribution extends GenericContinuousDistribution implements RandomGenerator, InterPacketTimeGenerator {

    /**
     * Equation de la densite: <BR>
     *    f(x)=a*lambda*x^(a-1)*exp(-lambda*x^a) pour x>0, avec a>0, lambda>0
     *       where lambda = risk variable; must be > 0
     *             a = explanatory variable; must be >= 1
     *  If a=1,the Weibull is the same as Exponential distribution with lambda=1/lambda
     */
    private static int A = 0;
    private static int LAMBDA = 1;

    /*
     * Definition des constructeurs
     */
    public WeibullDistribution() {
        paramsCount = 2;
        name = new String("Weibull");

        paramNames = new String[paramsCount];
        paramNames[A] = new String("a");
        paramNames[LAMBDA] = new String("lambda");

        params = new double[paramsCount];
        params[A] = 1.0d;
        params[LAMBDA] = 1.0d;
    }

    public WeibullDistribution(double unA, double unLambda)
            throws IllegalArgumentException {
        this();

        if ((unA > 0) && (unLambda > 0)) {
            setParam(A, unA);
            setParam(LAMBDA, unLambda);
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("WEIBULLDISTRIBUTION.CONSTRUCTOR_LAMBDA_AND_A_MUST_BE_GREATER_THAN_0."));
        }
    }

    public WeibullDistribution(double[] desParams)
            throws IllegalArgumentException {
        this();
        Console.println(
                Resource.getString("OK"));
        if (hasValidParam(desParams)) {
            setParams(desParams);
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("WEIBULLDISTRIBUTION.CONSTRUCTOR_LAMBDA_AND_A_MUST_BE_GREATER_THAN_0."));
        }
    }

    /**
     * Check that the parameters of the distribution are ok.
     * A >0 && lambda >=0
     * @param desParams distribution parameters
     * @return true if they match the requirements (see class descrption)
     */
    public boolean hasValidParam(double[] desParams) {
        if ((desParams[0] > 0) && (desParams[1] >= 0)) {
            return true;
        } else {
            return false;
        }
    }

    // Definitions de quelques methodes
    /**
     * Returns the value of the probability distribution function at x
     */
    // Densite
    public double pdf(double x) {
        if (x > 0) {
            return (((params[A] * params[LAMBDA]) * Math.pow(x, params[A]
                    - 1.0d)) / Math.exp(params[LAMBDA] * Math.pow(x, params[A])));
        } else {
            return 0d;
        }
    }

    /**
     * Returns the value of the cumulative distribution function at x
     */
    // Fonction de repartition: F(x)
    public double cdf(double x) {
        if (x > 0) {
            return (1.0d
                    - Math.exp(-1.0d * params[LAMBDA] * Math.pow(x, params[A])));
        } else {
            return 0d;
        }
    }

    /**
     * Return the value of Inverse Function at x
     * @param x with x>=0 and x<=1
     */
    public double inverseCdf(double x) throws IllegalArgumentException {
        // Attention Math.log: correspond bien au log en base e (i.e log neperien)
        if ((x >= 0) && (x <= 1)) {
            return (Math.pow(-Math.log(1 - x) / params[LAMBDA], 1 / params[A]));
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("WEIBULLDISTRIBUTION.INVERSECDF_X_MUST_BE_>_0_AND_<_1"));
        }
    }

    public double rand() {
        return this.inverseCdf(random.nextDouble());
    }

    public double nextInterPacketTime() {
        return rand();
    }

    /**
     * Returns the mean of the distribution
     */
    public double mean() {
        return Math.pow(params[LAMBDA], -1d / params[A]) * SpecialMath.gamma(1
                + (1.0 / params[A]));
    }

    /**
     * Returns the variance of the distribution
     */
    public double var() {
        double tmp;
        tmp = Math.pow(params[LAMBDA], -2.0d / params[A]) * (SpecialMath.gamma(1
                + (2.0d / params[A]))
                - Math.pow(SpecialMath.gamma(1d + (1d / params[A])), 2.0d));

        return tmp;
    }

    public String getEquation() {
        return new String(
                "f(x)=a*lambda*x^(a-1)*exp(-lambda*x^a) for x>0, with a>0, lambda>0");

        //         where lambda = risk variable; must be > 0 
        //             a = explanatory variable; must be >= 1 
        //  If a=1,the Weibull is the same as Exponential distribution with lambda=1/lambda")
    }

    public void setParameters(HashMap parameters) {
        double a = Double.parseDouble((String) parameters.get("a"));
        double mean;
        if (parameters.get("mean") != null) {
            mean = Double.parseDouble((String) parameters.get("mean"));
        } else {
            mean = 1.0d / Double.parseDouble((String) parameters.get("rate"));
        }
        double lambda = Math.pow(mean / SpecialMath.gamma(1 + (1.0 / a)), -a);
        if ((a > 0) && (lambda > 0)) {
            setParam(A, a);
            setParam(LAMBDA, lambda);
        }
    }

    private double nextStepA(double a, Inference inf, double A) {
        double a1, valor, B = 0, C = 0, H = 0;
        String nextString;
        try {
            inf.rewind();
            while ((nextString = inf.nextValue()) != null) {
                try {
                    valor = Double.parseDouble(nextString);
                    double aux = Math.pow(valor, a);
                    double aux2 = Math.log(valor);
                    B += aux;
                    C += aux * aux2;
                    H += aux * aux2 * aux2;
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        a1 = a + ((A + (1 / a) - (C / B)) / ((1 / (a * a)) + ((B * H - (C * C)) / (B * B))));
        return a1;
    }

    public void estimateParameters(Inference inf) {
        double aux1 = 0, aux2 = 0, valor, ak, ak1;
        String nextString;
        try {
            inf.rewind();
            while ((nextString = inf.nextValue()) != null) {
                try {
                    valor = Double.parseDouble(nextString);
                    double aux3 = Math.log(valor);
                    aux1 += aux3;
                    aux2 += aux3 * aux3;
                } catch (NumberFormatException e) {
                }
            }
            long n = inf.getSampleSize();
            double A = aux1 / n;
            ak = Math.pow(((6 / (Math.PI * Math.PI)) * (aux2 - ((aux1 * aux1) / n))) / (n - 1), -0.5);
            ak1 = nextStepA(ak, inf, A);
            while (Math.abs(ak1 - ak) > 0.001) {
                ak = ak1;
                ak1 = nextStepA(ak, inf, A);
            }
            setParam(WeibullDistribution.A, ak1);
            inf.rewind();
            double lamb = 0;
            while ((nextString = inf.nextValue()) != null) {
                try {
                    valor = Double.parseDouble(nextString);
                    lamb += Math.pow(valor, ak1);
                } catch (NumberFormatException e) {
                }
            }
            lamb = n / lamb;
            setParam(WeibullDistribution.LAMBDA, lamb);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /* //Old code with the testing unit and stuff YO!!!
    public static void main(String args[]){
    WeibullDistribution weibull = new WeibullDistribution(3.5,4.3);
    WeibullDistribution weibull2 = new WeibullDistribution();
    File log = new File("teste");
    for(int i=0; i<3000; i++){
    log.write(""+weibull.rand());
    }
    log.close();
    try {
    DataReader dr = new DataReader(log);
    Inference inf = new Inference(dr,0);
    weibull2.calculateMLE(inf);
    System.out.println(weibull2.getParam(0)+" "+weibull2.getParam(1));
    } catch (IOException e) {
    e.printStackTrace();
    }
    }*/
}
